﻿#include <QList>
#include <QMap>

#include <include/cef_app.h>
#include <include/cef_v8.h>
#include <include/cef_version.h>
#include <include/wrapper/cef_helpers.h>
#include <include/wrapper/cef_message_router.h>

#include "CefWebEngineCore/CefWebEngineProcess.h"

class CefViewRenderApp;
class V8Handler;

struct EventListener
{
    CefRefPtr<CefV8Value> callback;
    CefRefPtr<CefV8Context> context;
};

static QString obatinFrameId(CefRefPtr<CefFrame> frame)
{
#if CEF_VERSION_MAJOR >= 139
    return QString::fromStdString(frame->GetIdentifier());
#else
    return QString::number(frame->GetIdentifier());
#endif
}


class CefViewClient : public CefBaseRefCounted
{
public:
    CefViewClient(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Value> global,
        const QString &name);

    CefRefPtr<CefV8Value> CefValueToV8Value(CefValue *cefValue);
    CefRefPtr<CefValue> V8ValueToCefValue(CefV8Value *v8Value);
    void AsyncExecuteNativeMethod(const CefV8ValueList &arguments);
    void AsyncExecuteReportJSResult(const CefV8ValueList &arguments);
    void AddEventListener(const CefString &name, const EventListener &listener);
    void RemoveEventListener(const CefString &name, const EventListener &listener);
    void ExecuteEventListener(const CefString eventName, CefRefPtr<CefListValue> args);

private:
    QString mname;
    CefRefPtr<CefV8Value> mbridgeObject;
    CefRefPtr<CefV8Value> mreportJSResultFunction;
    CefRefPtr<CefBrowser> mbrowser;
    CefRefPtr<CefFrame> mframe;
    CefRefPtr<V8Handler> mv8Handler;
    QMap<CefString, QList<EventListener>> meventListenerListMap;

private:
    IMPLEMENT_REFCOUNTING(CefViewClient);
};

class V8Handler : public CefV8Handler
{
public:
    V8Handler(CefViewClient *client);

    virtual bool Execute(const CefString &function,
        CefRefPtr<CefV8Value> object,
        const CefV8ValueList &arguments,
        CefRefPtr<CefV8Value> &retval,
        CefString &exception) override;

protected:
    void ExecuteNativeMethod(CefRefPtr<CefV8Value> object,
        const CefV8ValueList &arguments,
        CefRefPtr<CefV8Value> &retval,
        CefString &exception);

    void ExecuteAddEventListener(CefRefPtr<CefV8Value> object,
        const CefV8ValueList &arguments,
        CefRefPtr<CefV8Value> &retval,
        CefString &exception);

    void ExecuteRemoveEventListener(CefRefPtr<CefV8Value> object,
        const CefV8ValueList &arguments,
        CefRefPtr<CefV8Value> &retval,
        CefString &exception);

    void ExecuteReportJSResult(CefRefPtr<CefV8Value> object,
        const CefV8ValueList &arguments,
        CefRefPtr<CefV8Value> &retval,
        CefString &exception);

private:
    CefViewClient *client_;

private:
    IMPLEMENT_REFCOUNTING(V8Handler);
};

V8Handler::V8Handler(CefViewClient *client)
    : client_(client)
{
}

bool V8Handler::Execute(const CefString &function,
    CefRefPtr<CefV8Value> object,
    const CefV8ValueList &arguments,
    CefRefPtr<CefV8Value> &retval,
    CefString &exception)
{
    if (function == "invokeMethod")
        ExecuteNativeMethod(object, arguments, retval, exception);
    else if (function == "addEventListener")
        ExecuteAddEventListener(object, arguments, retval, exception);
    else if (function == "removeEventListener")
        ExecuteRemoveEventListener(object, arguments, retval, exception);
    else if (function == "__cefview_report_js_result__")
        ExecuteReportJSResult(object, arguments, retval, exception);
    else
        return false;

    return true;
}

void V8Handler::ExecuteNativeMethod(CefRefPtr<CefV8Value> object,
    const CefV8ValueList &arguments,
    CefRefPtr<CefV8Value> &retval,
    CefString &exception)
{
    client_->AsyncExecuteNativeMethod(arguments);
    retval = CefV8Value::CreateUndefined();
}

void V8Handler::ExecuteAddEventListener(CefRefPtr<CefV8Value> object,
    const CefV8ValueList &arguments,
    CefRefPtr<CefV8Value> &retval,
    CefString &exception)
{
    if (arguments.size() != 2)
    {
        exception = "Invalid arguments; argument 2 must be a function";
        retval = CefV8Value::CreateBool(false);
        return;
    }
    
    if (!arguments[0]->IsString())
    {
        exception = "Invalid arguments; argument 1 must be a string";
        retval = CefV8Value::CreateBool(false);
        return;
    }
    
    if (!arguments[1]->IsFunction())
    {
        exception = "Invalid arguments; expecting 2 arguments";
        retval = CefV8Value::CreateBool(false);
        return;
    }
    
    CefString eventName = arguments[0]->GetStringValue();
    EventListener listener;
    listener.callback = arguments[1];
    listener.context = CefV8Context::GetCurrentContext();
    client_->AddEventListener(eventName, listener);
    retval = CefV8Value::CreateBool(true);
}

void V8Handler::ExecuteRemoveEventListener(CefRefPtr<CefV8Value> object,
    const CefV8ValueList &arguments,
    CefRefPtr<CefV8Value> &retval,
    CefString &exception)
{
    if (arguments.size() != 2)
    {
        exception = "Invalid arguments; argument 2 must be a function";
        retval = CefV8Value::CreateBool(false);
        return;
    }

    if (!arguments[0]->IsString())
    {
        exception = "Invalid arguments; argument 1 must be a string";
        retval = CefV8Value::CreateBool(false);
        return;
    }

    if (!arguments[1]->IsFunction())
    {
        exception = "Invalid arguments; expecting 2 arguments";
        retval = CefV8Value::CreateBool(false);
        return;
    }

    CefString eventName = arguments[0]->GetStringValue();
    EventListener listener;
    listener.callback = arguments[1];
    listener.context = CefV8Context::GetCurrentContext();
    client_->RemoveEventListener(eventName, listener);
    retval = CefV8Value::CreateBool(true);
}

void V8Handler::ExecuteReportJSResult(CefRefPtr<CefV8Value> object,
    const CefV8ValueList &arguments,
    CefRefPtr<CefV8Value> &retval,
    CefString &exception)
{
    retval = CefV8Value::CreateUndefined();

    if (arguments.size() != 2)
    {
        exception = "Invalid argument; argument 1 must be a double";
        return;
    }

    if (!arguments[0]->IsString())
    {
        exception = "Invalid argument; expecting 2 argument";
        return;
    }
    client_->AsyncExecuteReportJSResult(arguments);
}



CefViewClient::CefViewClient(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Value> global,
    const QString &name)
    : mname(name.isEmpty() ? "CefViewClient" : name)
    , mbridgeObject(nullptr)
    , mreportJSResultFunction(nullptr)
    , mbrowser(browser)
    , mframe(frame)
    , mv8Handler(new V8Handler(this))
{
    // create bridge object and mount it on the global context(window)
    mbridgeObject = CefV8Value::CreateObject(nullptr, nullptr);

    // create function "invokeMethod"
    CefRefPtr<CefV8Value> funcInvokeMethod = CefV8Value::CreateFunction("invokeMethod", mv8Handler);
    // add this function to window object
    mbridgeObject->SetValue("invokeMethod",
        funcInvokeMethod,
        static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY |
        V8_PROPERTY_ATTRIBUTE_DONTENUM |
        V8_PROPERTY_ATTRIBUTE_DONTDELETE));

    // create function addEventListener
    CefRefPtr<CefV8Value> funcAddEventListener =
        CefV8Value::CreateFunction("addEventListener", mv8Handler);
    // add this function to window object
    mbridgeObject->SetValue("addEventListener",
        funcAddEventListener,
        static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY |
        V8_PROPERTY_ATTRIBUTE_DONTENUM |
        V8_PROPERTY_ATTRIBUTE_DONTDELETE));

    // create function removeListener
    CefRefPtr<CefV8Value> funcRemoveEventListener =
        CefV8Value::CreateFunction("removeEventListener", mv8Handler);
    // add this function to window object
    mbridgeObject->SetValue("removeEventListener",
        funcRemoveEventListener,
        static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY |
        V8_PROPERTY_ATTRIBUTE_DONTENUM |
        V8_PROPERTY_ATTRIBUTE_DONTDELETE));

    // mount the client object to the global context(usually the window object)
    global->SetValue(mname.toStdU16String(),
        mbridgeObject,
        static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY |
        V8_PROPERTY_ATTRIBUTE_DONTENUM |
        V8_PROPERTY_ATTRIBUTE_DONTDELETE));

    QString code = "console.info('[JSRuntime]:window." + mname + " [object] created');";
    mframe->ExecuteJavaScript(code.toStdU16String(), mframe->GetURL(), 0);

    // create "__cefview_report_js_result__" function and mount it on the global context(window)
    mreportJSResultFunction = CefV8Value::CreateFunction("__cefview_report_js_result__", mv8Handler);
    global->SetValue("__cefview_report_js_result__",
        mreportJSResultFunction,
        static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY |
        V8_PROPERTY_ATTRIBUTE_DONTENUM |
        V8_PROPERTY_ATTRIBUTE_DONTDELETE));
    mframe->ExecuteJavaScript("console.info('[JSRuntime]:window.__cefview_report_js_result__ [function] created');",
        mframe->GetURL(),
        0);
}

CefRefPtr<CefV8Value> CefViewClient::CefValueToV8Value(CefValue *cefValue)
{
    CefRefPtr<CefV8Value> v8Value = CefV8Value::CreateNull();
    if (!cefValue) {
        return v8Value;
    }

    auto type = cefValue->GetType();
    switch (type) {
    case CefValueType::VTYPE_INVALID: {
        v8Value = CefV8Value::CreateUndefined();
    } break;
    case CefValueType::VTYPE_NULL: {
        v8Value = CefV8Value::CreateNull();
    } break;
    case CefValueType::VTYPE_BOOL: {
        auto v = cefValue->GetBool();
        v8Value = CefV8Value::CreateBool(v);
    } break;
    case CefValueType::VTYPE_INT: {
        auto v = cefValue->GetInt();
        v8Value = CefV8Value::CreateInt(v);
    } break;
    case CefValueType::VTYPE_DOUBLE: {
        auto v = cefValue->GetDouble();
        v8Value = CefV8Value::CreateDouble(v);
    } break;
    case CefValueType::VTYPE_STRING: {
        auto v = cefValue->GetString();
        v8Value = CefV8Value::CreateString(v);
    } break;
    case CefValueType::VTYPE_BINARY: {
        // TO-DO
        // currently not supported
    } break;
    case CefValueType::VTYPE_DICTIONARY: {
        auto cDict = cefValue->GetDictionary();
        CefDictionaryValue::KeyList cKeys;
        cDict->GetKeys(cKeys);
        v8Value = CefV8Value::CreateObject(nullptr, nullptr);
        for (auto &key : cKeys) {
            auto cVal = cDict->GetValue(key);
            auto v8Val = CefValueToV8Value(cVal.get());
            v8Value->SetValue(key, v8Val.get(), V8_PROPERTY_ATTRIBUTE_NONE);
        }
    } break;
    case CefValueType::VTYPE_LIST: {
        auto cList = cefValue->GetList();
        int cCount = static_cast<int>(cList->GetSize());
        v8Value = CefV8Value::CreateArray(static_cast<int>(cCount));
        for (int i = 0; i < cCount; i++) {
            auto cVal = cList->GetValue(i);
            auto v8Val = CefValueToV8Value(cVal.get());
            v8Value->SetValue(i, v8Val.get());
        }
    } break;
    default:
        break;
    }

    return v8Value;
}

CefRefPtr<CefValue> CefViewClient::V8ValueToCefValue(CefV8Value *v8Value)
{
    CefRefPtr<CefValue> cefValue = CefValue::Create();
    if (!v8Value) {
        return cefValue;
    }

    /**
     * The IsDouble, IsInt and IsUint methods return a boolean value indicating whether the CefV8Value instance is an
     * target type or can be converted to the target type.If the value can be converted to the target type, the methods
     * will attempt to do so and return true. If the value is not the target type or cannot be converted to the target
     * type, the method will return false.
     *
     * For example the code below:
     *   auto v = CefV8Value::CreateInt(1000);
     *   auto isDouble = v->IsDouble();
     *   logD("isDouble: %d", isDouble);  // true
     *   auto isUnint = v->IsUInt();
     *   logD("isUnint: %d", isUnint);    // true
     *   auto isInt = v->IsInt();
     *   logD("isInt: %d", isInt);        // true
     *
     *   auto v = CefV8Value::CreateDouble(0.1);
     *   auto isDouble = v->IsDouble();
     *   logD("isDouble: %d", isDouble);  // true
     *   auto isUnint = v->IsUInt();
     *   logD("isUnint: %d", isUnint);    // false
     *   auto isInt = v->IsInt();
     *   logD("isInt: %d", isInt);        // false
     *
     * so we need to keep the testing order, IsInt/IsUint - IsDouble
     * since there is no Uint type in JavaScript, we just ignore it.
     * Please refer to this test souce:
     * https://github.com/svn2github/cef/blob/master/tests/cefclient/binding_test.cpp
     */
    if (v8Value->IsNull() || v8Value->IsUndefined())
        cefValue->SetNull();
    else if (v8Value->IsBool())
        cefValue->SetBool(v8Value->GetBoolValue());
    else if (v8Value->IsInt())
        cefValue->SetInt(v8Value->GetIntValue());
    else if (v8Value->IsDouble())
        cefValue->SetDouble(v8Value->GetDoubleValue());
    else if (v8Value->IsString())
        cefValue->SetString(v8Value->GetStringValue());
    else if (v8Value->IsArrayBuffer()) {
        // TO-DO
        // currently not supported
    }
    else if (v8Value->IsArray())
    {
        auto s = v8Value->GetArrayLength();
        auto cefList = CefListValue::Create();
        for (int i = 0; i < s; i++)
        {
            auto v8Val = v8Value->GetValue(i);
            auto cefVal = V8ValueToCefValue(v8Val.get());
            cefList->SetValue(i, cefVal);
        }
        cefValue->SetList(cefList);
    }
    else if (v8Value->IsObject())
    {
        CefDictionaryValue::KeyList keys;
        v8Value->GetKeys(keys);
        auto cefDict = CefDictionaryValue::Create();
        for (auto &key : keys)
        {
            auto v8Val = v8Value->GetValue(key);
            auto cefVal = V8ValueToCefValue(v8Val.get());
            cefDict->SetValue(key, cefVal.get());
        }
        cefValue->SetDictionary(cefDict);
    }
    else {
        cefValue->SetNull();
    }
    
    return cefValue;
}

void CefViewClient::AsyncExecuteNativeMethod(const CefV8ValueList &arguments)
{
    CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("CefViewClientRender.InvokeMethod");

    //** arguments(CefValueList)
    //** +-------+
    //** |0 name | <- the method name
    //** |1 arg1 |
    //** |2 arg2 |
    //** |3 arg3 |
    //** |4 arg4 |
    //** | ...   |
    //** | ...   |
    //** | ...   |
    //** | ...   |
    //** +-------+
    CefRefPtr<CefListValue> args = msg->GetArgumentList();

    // push back all the arguments
    for (std::size_t i = 0; i < arguments.size(); i++) {
        auto cefValue = V8ValueToCefValue(arguments[i].get());
        args->SetValue(i, cefValue);
    }

    // send the message
    if (mbrowser)
        mframe->SendProcessMessage(PID_BROWSER, msg);
}

void CefViewClient::AsyncExecuteReportJSResult(const CefV8ValueList &arguments)
{
    CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("CefViewClientRender.ReportJSResult");

    //** arguments(CefValueList)
    //** +_------+
    //** |0 arg  | <- the context (string)
    //** |1 arg  | <- the result value
    //** +-------+
    CefRefPtr<CefListValue> args = msg->GetArgumentList();

    // push back the result value
    for (std::size_t i = 0; i < arguments.size(); i++)
    {
        auto cefValue = V8ValueToCefValue(arguments[i].get());
        args->SetValue(i, cefValue.get());
    }

    // send the message
    if (mbrowser)
        mframe->SendProcessMessage(PID_BROWSER, msg);
}

void CefViewClient::AddEventListener(const CefString &name, const EventListener &listener)
{
    auto itListenerList = meventListenerListMap.find(name);
    if (itListenerList == meventListenerListMap.end())
    {
        QList<EventListener> eventListenerList;
        eventListenerList.append(listener);
        meventListenerListMap[name] = eventListenerList;
    }
    else
    {
        QList<EventListener> &eventListenerList = itListenerList.value();
        // does this listener exist?
        bool found = false;
        for (auto item : eventListenerList)
        {
            if (item.callback->IsSame(listener.callback))
            {
                found = true;
                break;
            }
        }

        if (!found)
            eventListenerList.push_back(listener);
    }
}

void CefViewClient::RemoveEventListener(const CefString &name, const EventListener &listener)
{
    auto itListenerList = meventListenerListMap.find(name);
    if (itListenerList != meventListenerListMap.end())
    {
        QList<EventListener> &eventListenerList = itListenerList.value();
        for (auto itListener = eventListenerList.begin(); itListener != eventListenerList.end(); itListener++)
        {
            if (itListener->callback->IsSame(listener.callback))
            {
                eventListenerList.erase(itListener);
                break;
            }
        }
    }
}

void CefViewClient::ExecuteEventListener(const CefString eventName, CefRefPtr<CefListValue> args)
{
    auto itListenerList = meventListenerListMap.find(eventName);
    if (itListenerList == meventListenerListMap.end())
        return;

    QList<EventListener> &eventListenerList = itListenerList.value();
    for (auto listener : eventListenerList)
    {
        listener.context->Enter();

        CefV8ValueList v8ArgList;
        for (size_t i = 0; i < args->GetSize(); i++)
        {
            auto cefValue = args->GetValue(i);
            auto v8Value = CefValueToV8Value(cefValue.get());
            v8ArgList.push_back(v8Value.get());
        }

        listener.callback->ExecuteFunction(mbridgeObject, v8ArgList);
        listener.context->Exit();
    }
}



class CefViewRenderApp : public CefApp, public CefRenderProcessHandler
{
public:
    CefViewRenderApp(const QString &bridgeName);
    ~CefViewRenderApp();

private:
    CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override;

    void OnWebKitInitialized() override;
    void OnBrowserCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefDictionaryValue> extra_info) override;
    void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override;
    CefRefPtr<CefLoadHandler> GetLoadHandler() override;
    void OnContextCreated(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Context> context) override;

    void OnContextReleased(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Context> context) override;

    void OnUncaughtException(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Context> context,
        CefRefPtr<CefV8Exception> exception,
        CefRefPtr<CefV8StackTrace> stackTrace) override;

    void OnFocusedNodeChanged(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefDOMNode> node) override;

    bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefProcessId source_process,
        CefRefPtr<CefProcessMessage> message) override;

protected:
    bool OnTriggerEventNotifyMessage(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefProcessId source_process,
        CefRefPtr<CefProcessMessage> message);

    void ExecuteEventListener(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        const CefString &name,
        CefRefPtr<CefListValue> args);
    
private:
    QString mbridgeName;
    CefRefPtr<CefMessageRouterRendererSide> mrenderMessageRouter;
    QMap<QString, CefRefPtr<CefViewClient>> mframeIdClientMap;
    bool mlastNodeIsEditable;

    IMPLEMENT_REFCOUNTING(CefViewRenderApp);
};

CefViewRenderApp::CefViewRenderApp(const QString &bridgeName)
    : mbridgeName(bridgeName)
{
}

CefViewRenderApp::~CefViewRenderApp()
{
}

CefRefPtr<CefRenderProcessHandler> CefViewRenderApp::GetRenderProcessHandler()
{
    return this;
}

void CefViewRenderApp::OnWebKitInitialized()
{
    CEF_REQUIRE_RENDERER_THREAD();

    CefMessageRouterConfig config;
    config.js_query_function = "CefViewQuery";
    config.js_cancel_function = "CefViewQueryCancel";
    mrenderMessageRouter = CefMessageRouterRendererSide::Create(config);
}

void CefViewRenderApp::OnBrowserCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefDictionaryValue> extra_info)
{
    CEF_REQUIRE_RENDERER_THREAD();
}

void CefViewRenderApp::OnBrowserDestroyed(CefRefPtr<CefBrowser> browser)
{
    CEF_REQUIRE_RENDERER_THREAD();
}

CefRefPtr<CefLoadHandler> CefViewRenderApp::GetLoadHandler()
{
    return nullptr;
}

void CefViewRenderApp::OnContextCreated(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context)
{
    CEF_REQUIRE_RENDERER_THREAD();

    // [Javascript Context]
    // V8 context for this frame has been initialized already,
    // but the script of the page hasn't been executed now
    mrenderMessageRouter->OnContextCreated(browser, frame, context);

    // log this event
    frame->ExecuteJavaScript("console.info('[JSRuntime]:frame context created')", frame->GetURL(), 0);

    // binding bridge object and functions
    QString frameId = obatinFrameId(frame);
    if (!mframeIdClientMap.contains(frameId))
    {
        CefRefPtr<CefV8Value> objWindow = context->GetGlobal();
        CefRefPtr<CefViewClient> objClient = new CefViewClient(browser, frame, objWindow, mbridgeName);
        if (!objClient)
        {
            qInfo("Failed to create the client object");
            return;
        }
        mframeIdClientMap[frameId] = objClient;
    }
}

void CefViewRenderApp::OnContextReleased(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context)
{
    CEF_REQUIRE_RENDERER_THREAD();
    mrenderMessageRouter->OnContextReleased(browser, frame, context);
    mframeIdClientMap.remove(obatinFrameId(frame));
}

void CefViewRenderApp::OnUncaughtException(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context,
    CefRefPtr<CefV8Exception> exception,
    CefRefPtr<CefV8StackTrace> stackTrace)
{
    CEF_REQUIRE_RENDERER_THREAD();
}

void CefViewRenderApp::OnFocusedNodeChanged(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefDOMNode> node)
{
    CEF_REQUIRE_RENDERER_THREAD();

    bool isEditable = (node.get() && node->IsEditable());
    if (isEditable != mlastNodeIsEditable)
    {
        // Notify the browser of the change in focused element type.
        mlastNodeIsEditable = isEditable;
        CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create("kCefViewClientRenderFocusedNodeChangedMessage");
        message->GetArgumentList()->SetBool(0, isEditable);
        frame->SendProcessMessage(PID_BROWSER, message);
    }
}

bool CefViewRenderApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefProcessId source_process,
    CefRefPtr<CefProcessMessage> message)
{
    CEF_REQUIRE_RENDERER_THREAD();
    DCHECK_EQ(source_process, PID_BROWSER);

    if (mrenderMessageRouter->OnProcessMessageReceived(browser, frame, source_process, message))
        return true;
    
    if (OnTriggerEventNotifyMessage(browser, frame, source_process, message))
        return true;
    
    return false;
}

bool CefViewRenderApp::OnTriggerEventNotifyMessage(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
    if (message->GetName() == "CefViewClientBrowser.TriggerEvent")
    {
        CefRefPtr<CefListValue> args = message->GetArgumentList()->Copy();
        //** arguments(CefValueList)
        //** +------------+
        //** | event name |
        //** | event arg1 |
        //** | event arg2 |
        //** | event arg3 |
        //** | event arg4 |
        //** |    ...     |
        //** |    ...     |
        //** |    ...     |
        //** |    ...     |
        //** +------------+
        if (!args || args->GetSize() <= 0)
        {
            qInfo("Invalid message arguments, event name is required");
            return true;
        }

        if (CefValueType::VTYPE_STRING != args->GetType(0))
        {
            qInfo("Invalid message arguments, invalid type for event name");
            return true;
        }

        CefString name = args->GetString(0);
        args->Remove(0);
        ExecuteEventListener(browser, frame, name, args);

        return true;
    }

    return false;
}

void CefViewRenderApp::ExecuteEventListener(CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame, const CefString &name, CefRefPtr<CefListValue> args)
{
    if (browser && frame)
    {
        QString frameId = obatinFrameId(frame);
        if (mframeIdClientMap.contains(frameId))
        {
            const CefRefPtr<CefViewClient> &objClient = mframeIdClientMap[frameId];
            objClient->ExecuteEventListener(name, args);
        }
    }
}


class CefViewOtherApp : public CefApp
{
public:
    CefViewOtherApp();

private:
    IMPLEMENT_REFCOUNTING(CefViewOtherApp);
};

CefViewOtherApp::CefViewOtherApp()
{
}


CefWebEngineProcess::CefWebEngineProcess(QObject *parent)
    : QObject(parent)
{
}

CefWebEngineProcess::~CefWebEngineProcess()
{
}

int CefWebEngineProcess::run()
{
#if CEF_VERSION_MAJOR < 112
    CefEnableHighDPISupport();
#endif
    
    CefRefPtr<CefCommandLine> commandLine = CefCommandLine::CreateCommandLine();
    commandLine->InitFromString(::GetCommandLineW());
    if (!commandLine->HasSwitch("type"))
        return 1;

    CefRefPtr<CefApp> app;
    CefString type = commandLine->GetSwitchValue("type");
    if (type == "zygote" || type == "renderer")
    {
        CefString name = commandLine->GetSwitchValue("bridge-obj-name");
        app = new CefViewRenderApp(QString::fromStdWString(name.ToWString()));
    }
    else {
        app = new CefViewOtherApp();
    }
    return CefExecuteProcess(CefMainArgs(GetModuleHandle(nullptr)), app, nullptr);
}
